
#pragma once

#include <cstdint>

#include "./wire_interface.hpp"

namespace twixx {

    /**
     * @brief 用于单向传输OLED 数据的软件I2C 实现
     *
     * 需要保证SCL 和SDA 处于开漏输出状态。
     *
     * SSD13 系列的单色OLED 有比较高的I2C 通信频率，引脚操作可以完全不管电平维持时间。
     * 如果频率太高，有必要降低时，可以设置宏_IOXX_WRITE_ONLY_I2C_EXTRA_TIMING
     *
     */
    template <typename PinType>
    class WriteOnlyWire : public IWireWriter {
       private:
        const PinType &_scl;
        const PinType &_sda;

       public:
        WriteOnlyWire(const PinType &scl_pin, const PinType &sda_pin) :
            _scl(scl_pin), _sda(sda_pin) {}

        // uint_fast8_t _slave_addr_left_shifted = 0;


        /**
         * @brief 发出起始信号，调用后SCL 为低
         *
         */
        void _signal_start() {
            _sda.set();
            _scl.set();

#ifdef _IOXX_WRITE_ONLY_I2C_EXTRA_TIMING
            _scl.set();
            _scl.set();
#endif
            _sda.clr();
            _sda.clr();

            _scl.clr();
            _scl.clr();

            // TODO: 频率控制
        }

        /**
         * @brief 发出终止信号，调用后SCL 为高电平
         *
         */
        void _signal_stop() {
            _scl.clr();
            _sda.clr();
            _scl.set();

#ifdef _IOXX_WRITE_ONLY_I2C_EXTRA_TIMING
            _scl.set();
            _scl.set();
#endif
            _sda.set();

            // TODO: 频率控制
        }

        /**
         * @brief 发送一字节，调用后SCL 为低电平，SDA 高电平
         *
         * @param bit_8
         */
        void _tx(uint_fast8_t bit_8) {
            for (auto i = 0; i < 8; i++) {
                if(bit_8 & 0x80) {
                    _sda.set();
                }
                else {
                    _sda.clr();
                }

                bit_8 = bit_8 << 1;

                _scl.set();
                _scl.set();
                _scl.set();
                _scl.clr();
                _scl.clr();
                _scl.clr();
            }

              // 额外的一个时钟，不处理应答信号
            _scl.set();
            _scl.set();
            _scl.set();
            _scl.clr();
            _sda.clr();
            _scl.clr();

            //             // I2C 标准发送顺序是msb first
            //             for (uint_fast8_t i = 8; i > 0; --i) {
            //                 _scl.clr();
            //                 _scl.clr();

            //                 if(bit_8 & 0x80) {
            //                     _sda.set();
            //                     _sda.set();
            //                 }
            //                 else {
            //                     _sda.clr();
            //                     _sda.clr();
            //                 }

            //                 bit_8 = bit_8 << 1;
            //                 _scl.set();

            // #ifdef _IOXX_WRITE_ONLY_I2C_EXTRA_TIMING
            //                 _scl.set();
            //                 _scl.set();
            // #endif
            //             }

            //             // 一个字节发完后，_scl 会是高电平
            //             //  拉低SCL，让从机发送ACK 或NACK，但是不判断
            //             // 所以不需要释放SDA
            //             _scl.clr();
            //             _sda.set();

            // #ifdef _IOXX_WRITE_ONLY_I2C_EXTRA_TIMING
            //             _scl.clr();  // 重复拉低，就当是给从机多一点反应时间
            // #endif
            //             _scl.set();
        }


       public:
        /**
         * @brief 准备写入，发送起始信号，选中指定从机
         *
         * @param slave_addr 采用与Wire 库相同的从机地址格式，7 位地址右对齐
         *
         * @return uint8_fast8_t
         */
        uint_fast8_t begin_tx(uint_fast8_t slave_addr) {
            uint_fast8_t slave_addr_left_shifted = slave_addr << 1;

            _signal_start();
            _tx(slave_addr_left_shifted);

            return 0;
        }

        /**
         * @brief 结束传输，发送终止信号
         *
         * @return uint8_fast8_t
         */
        uint_fast8_t end() {
            _signal_stop();

            return 0;
        }

        /**
         * @brief 写1 字节
         *
         * @param b
         * @return uint_fast8_t
         */
        uint_fast8_t write(uint_fast8_t b) {
            _tx(b);

            return 1;
        }


        /**
         * @brief 连续写入多个字节
         *
         * @param data_ptr
         * @param count
         * @return uint_fast8_t
         */
        uint_fast8_t write(uint_fast8_t *data_ptr, uint_fast8_t count) {
            for (; count > 0; --count) {
                _tx(*data_ptr);
                ++data_ptr;
            }

            return count;
        }
    };

}  // namespace ioxx